﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Globalization;
using ExpressionEvaluator.Expceptions;
using System.Reflection;

namespace ExpressionEvaluator.BuilderTreeNode
{
	/// <summary>
	/// Node containing normal expression (constant or base type and member access)
	/// </summary>
	internal class ExpressionNode : BuilderTreeNodeBase
	{
		private const string cNullValue = "null";

		private static Dictionary<string, Expression> WellKnownConstants;
		private static Dictionary<Type, int> TypeCompareValuesTable;

		static ExpressionNode()
		{
			// initialize well know constants
			WellKnownConstants = new Dictionary<string, Expression>(3);
			
			WellKnownConstants.Add("null", Expression.Constant(null));
			WellKnownConstants.Add("true", Expression.Constant(true));
			WellKnownConstants.Add("false", Expression.Constant(false));

			// initializ type compare dictionary with type as key and value as strength
			TypeCompareValuesTable = new Dictionary<Type,int>();

			TypeCompareValuesTable.Add(typeof(Boolean), 1);
			TypeCompareValuesTable.Add(typeof(Byte), 1);
			TypeCompareValuesTable.Add(typeof(UInt16), 2);
			TypeCompareValuesTable.Add(typeof(Int16), 3);
			TypeCompareValuesTable.Add(typeof(UInt32), 4);
			TypeCompareValuesTable.Add(typeof(Int32), 5);
			TypeCompareValuesTable.Add(typeof(UInt64), 6);
			TypeCompareValuesTable.Add(typeof(Int64), 7);
			TypeCompareValuesTable.Add(typeof(float), 8);
			TypeCompareValuesTable.Add(typeof(double), 9);
		}

		/// <summary>
		/// Gets or sets the operator.
		/// </summary>
		/// <value>The operator.</value>
		public ExpressionOperator Operator
		{
			get;
			internal set;
		}

		/// <summary>
		/// Gets or sets the operator code block.
		/// </summary>
		/// <value>The operator code block.</value>
		public CodeBlock OperatorCodeBlock
		{
			get;
			internal set;
		}

		/// <summary>
		/// Sets the operator.
		/// </summary>
		/// <param name="op">The operator.</param>
		/// <param name="opCodeBlock">The operator code block.</param>
		public void SetOperator(ExpressionOperator op, CodeBlock opCodeBlock)
		{
			Operator = op;
			OperatorCodeBlock = opCodeBlock;
		}

		/// <summary>
		/// Moves the last ident to function.
		/// </summary>
		/// <param name="node">The node.</param>
		/// <returns><c>True</c> if success; else <c>false</c>.</returns>
		public bool MoveLastIdentToFunction(FunctionOrPropertyNode node)
		{
			if (Idents.Count > 2)
			{
				CodeBlock lastIdent = Idents[Idents.Count - 1];
				// move last ident to function node
				Idents.Remove(lastIdent);
				node.Idents.Add(lastIdent);
				// remove dot part
				Idents.RemoveAt(Idents.Count - 1);
				return true;
			}
			return false;
		}

		/// <summary>
		/// Gets the expression.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <returns>Expression value of this node.</returns>
		public override Expression GetExpression(BuilderTree tree)
		{
			// if operator specified, then expression node contains binary expression
			if (Operator != null)
			{
				Expression leftExpression = Nodes[0].GetExpression(tree);
				Expression rightExpession = Nodes[1].GetExpression(tree);
				// if left and right is not same type
				if ((leftExpression.Type != rightExpession.Type) 
					// skipp null values
					&& (leftExpression.Type != typeof(object))
					&& (rightExpession.Type != typeof(object)))
				{
					if (!TypeCompareValuesTable.ContainsKey(leftExpression.Type) || !TypeCompareValuesTable.ContainsKey(rightExpession.Type))
					{
						throw new EvalException("Cennot create binary expression for left type '{0} and right type '{1}' because type convert is unknown!", OperatorCodeBlock);
					}
					if (TypeCompareValuesTable[leftExpression.Type] > TypeCompareValuesTable[rightExpession.Type])
					{
						rightExpession = Expression.Convert(rightExpession, leftExpression.Type);
					}
					else
					{
						leftExpression = Expression.Convert(leftExpression, rightExpession.Type);
					}
				}
				return Operator.BinaryExpression(leftExpression, rightExpession);
			}
			else
			{
				// if no idents, then this is just go through expression
				if (Idents.Count == 0)
				{
					return Nodes[0].GetExpression(tree);
				}
				// if first char is digit, means it is number
				if (IsDigit(Idents[0].Text))
				{
					return CreateNumberConstant();
				}
				// if value is null value
				else if ((Idents.Count == 1) && WellKnownConstants.ContainsKey(Idents[0].Text.ToLower()))
				{
					return WellKnownConstants[Idents[0].Text.ToLower()];
				}
				else
				{
					string currentName = string.Empty;
					Expression currentExpression = null;
					Type currentType = null;
					// iterate through idents
					for (int i = 0; i < Idents.Count; ++i)
					{
						// add dot for odd idents to current name otherwise skip
						if ((i % 2) == 1)
						{
							currentName += ".";
							continue;
						}
						// if expression not yet evaluated
						if (currentType == null)
						{
							currentName += Idents[i].Text;
							// if only first ident specified
							if (i == 0)
							{
								// try to find in parameters
								currentExpression = FindParameter(tree, currentName);
								// if not parameter try to find in registered namespaces
								if (currentExpression == null)
								{
									currentType = TryToFindTypeInRegisteredNamespaces(currentName, tree);
								}
								else
								{
									currentType = currentExpression.Type;
								}
							}
							// else try to find fully specified type in registered assemblies
							else
							{
								currentType = TryToFindTypeInRegisteredAssemblies(currentName, tree);
							}
						}
						// if we have type, and we don't have expression => find property or field based on type
						else if (currentExpression == null)
						{
							currentExpression = GetPropertyOrFieldExpression(currentType, Idents[i].Text); 
						}
						// all next items are fields or properties based on expression
						else
						{
							currentExpression = Expression.PropertyOrField(currentExpression, Idents[i].Text);
						}
					}
					// now process all function, properties and indexer nodes
					foreach (BuilderTreeNodeBase node in Nodes)
					{
						if (node is OpaqueExpressionNode)
						{
							// if we have expression then process expression based on expression
							if (currentExpression != null)
							{
								currentExpression = (node as OpaqueExpressionNode).GetParentExpression(currentExpression, tree);
							}
							// else then process expression based on type
							else
							{
								currentExpression = (node as OpaqueExpressionNode).GetParentExpression(currentType, tree);
							}
						}
						else
						{
							throw new EvalException("SubNode of Expression should by only Opaque node (like function or index)", Idents[Idents.Count - 1]);
						}
					}
					return currentExpression;
				}
			}
		}

		/// <summary>
		/// Determines whether the specified value is digit.
		/// </summary>
		/// <param name="value">The value.</param>
		/// <returns>
		/// 	<c>true</c> if the specified value is digit; otherwise, <c>false</c>.
		/// </returns>
		private bool IsDigit(string value)
		{
			// if first char is digit or first is minus sign, means it is number
			return char.IsDigit(value[0]) || (value[0].ToString() == ExpressionTreeBuilder.cMinusSign);
		}

		/// <summary>
		/// Gets the property or field expression.
		/// </summary>
		/// <param name="type">The type.</param>
		/// <param name="propertyOrField">The property or field.</param>
		/// <returns>Property or field expression.</returns>
		internal static Expression GetPropertyOrFieldExpression(Type type, string propertyOrField)
		{
			PropertyInfo property = type.GetProperty(propertyOrField);
			if (property != null)
			{
				return Expression.MakeMemberAccess(null, property);
			}
			else
			{
				FieldInfo field = type.GetField(propertyOrField);
				return Expression.MakeMemberAccess(null, field);
			}
		}

		/// <summary>
		/// Tries to find type in registered namespaces.
		/// </summary>
		/// <param name="currentName">Name of the current type.</param>
		/// <param name="tree">The tree.</param>
		/// <returns>If type with specified name exists in registered namespaces, then type, else null.</returns>
		internal static Type TryToFindTypeInRegisteredNamespaces(string currentName, BuilderTree tree)
		{
			foreach (string namespacePattern in tree.LookupNamespaces)
			{
				Type type = Type.GetType(string.Format(namespacePattern, currentName), false);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}

		/// <summary>
		/// Tries to find type in registered assemblies.
		/// </summary>
		/// <param name="currentName">Name of the current type.</param>
		/// <param name="tree">The tree.</param>
		/// <returns>If type with specified name exists in registered assemblies, then type, else null.</returns>
		internal static Type TryToFindTypeInRegisteredAssemblies(string currentName, BuilderTree tree)
		{
			foreach (string assembly in tree.LookupAssemblies)
			{
				Type type = Type.GetType(string.Format("{0}, {1}", currentName, assembly), false);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}

		/// <summary>
		/// Finds the parameter.
		/// </summary>
		/// <param name="tree">The tree.</param>
		/// <param name="name">The parameter name.</param>
		/// <returns><c>Parameter</c> if founded, else <c>null</c>.</returns>
		private ParameterExpression FindParameter(BuilderTree tree, string name)
		{
			if (tree.Parameters != null)
			{
				return tree.Parameters.Where(par => par.Name == name).FirstOrDefault();
			}
			return null;
		}

		/// <summary>
		/// Creates the constant expression.
		/// </summary>
		/// <returns>Constant expression with parsed number.</returns>
		private Expression CreateNumberConstant()
		{
			string number = string.Empty;
			Idents.ForEach(value => number += value.Text);
			try
			{
				return Expression.Constant(ParseNumber(number));
			}
			catch (Exception e)
			{
				throw new EvalException(
						string.Format("Unable to parse number '{0}'.", number),
						Idents[0], e);
			}
		}

		/// <summary>
		/// Parses the number from text.
		/// </summary>
		/// <param name="text">The text.</param>
		/// <returns></returns>
		private static object ParseNumber(string text)
		{
			text = text.ToLowerInvariant();
			// if decimal number
			if (text.Contains('.'))
			{
				if (char.IsDigit(text[text.Length - 1]))
				{
					return double.Parse(text, CultureInfo.InvariantCulture);
				}
				if (text.EndsWith("f"))
				{
					return float.Parse(text, CultureInfo.InvariantCulture);
				}
				else if (text.EndsWith("d"))
				{
					return double.Parse(text, CultureInfo.InvariantCulture);
				}
			}
			// else normal number
			else
			{
				if (char.IsDigit(text[text.Length - 1]))
				{
					var val = Int64.Parse(text, CultureInfo.InvariantCulture);
                    if (val > Int32.MaxValue)
                    {
                        return val;
                    }
                    else
                    {
                        return (int) val;
                    }
				}
				if (text.EndsWith("b"))
				{
					return Byte.Parse(text, CultureInfo.InvariantCulture);
				}
				
				else if (text.EndsWith("us"))
				{
					return UInt16.Parse(text, CultureInfo.InvariantCulture);
				}
				else if (text.EndsWith("s"))
				{
					return Int16.Parse(text, CultureInfo.InvariantCulture);
				}
				else if (text.EndsWith("u"))
				{
					return UInt32.Parse(text, CultureInfo.InvariantCulture);
				}
				else if (text.EndsWith("ul"))
				{
					return UInt64.Parse(text, CultureInfo.InvariantCulture);
				}
				else if (text.EndsWith("l"))
				{
					return Int64.Parse(text, CultureInfo.InvariantCulture);
				}
			}
			throw new EvalException(
						string.Format("No converter found for '{0}'.", text),
						null);
		}

		/// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <remarks>
		/// Can be used for tracing bugs.
		/// </remarks>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </returns>
		public override string ToString()
		{
			StringBuilder result = new StringBuilder();
			Idents.ForEach(code => result.Append(code.Text));

			if (Nodes.Count > 0)
			{
				if (Operator != null)
				{
					result.Append("(");
					result.Append(Nodes[0].ToString());

					if (OperatorCodeBlock != null)
					{
						result.Append(OperatorCodeBlock.Text);
					}
					if (Nodes.Count > 1)
					{
						for (int i = 1; i < Nodes.Count; ++i)
						{
							result.Append(Nodes[i].ToString());
						}
					}
					result.Append(")");
				}
				else
				{
					Nodes.ForEach(node => result.Append(node.ToString()));
				}
			}
			else if (OperatorCodeBlock != null)
			{
				result.Append("ERROR");
			}
			return result.ToString();
		}
	}

}
